package org.hackystat.devcathlon.engine.event.impl;

import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.dailyprojectdata.resource.coverage.jaxb.CoverageDailyProjectData;
import org.hackystat.devcathlon.engine.event.Event;
import org.hackystat.devcathlon.engine.event.EventException;
import org.hackystat.devcathlon.engine.event.EventScore;
import org.hackystat.devcathlon.engine.match.Match;
import org.hackystat.devcathlon.engine.team.Team;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * 
 * Reward teams that improve their coverage.
 * <p>
 * Requires the following properties in the configuration.xml file for this event:
 * <ul>
 * <li>WakeupInterval: How frequently this event is invoked, and how far back it looks for data.
 * <li>BaseScoreWeight: An integer multiplied by the base score to get the base points.
 * <li>TypeOfCoverage: A string indicating the kind of coverage to retrieve, such as "Line".
 * <li>PercentImprovement: An integer indicating the minimum amount of improvement to recieve/lose
 * points.
 * <li>BonusPercentImprovement: An integer indicating the minimum amount of improvement to get
 * bonus.
 * <li>BonusImprovedDays: An integer for the number of days of improvements
 * <li>BonusScoreWeight: An integer multiplied by base score to get bonus points.
 * </ul>
 * <p>
 * Specification:
 * <ul>
 * <li>Wakeup once every WakeupInterval.
 * <li>Retrieves TypeOfCoverage.
 * <li>If coverage has improved at least PercentImprovement award BaseScoreWeight.
 * <li>If coverage has declined at least PercentImprovement deduct BaseScoreWeight.
 * </ul>
 * </p>
 * <p>
 * Incentives:
 * <ul>
 * <li>If coverage increased by at least BonusPercentImprovment for the past BonusImprovedDays then
 * award BonusScoreWeight.
 * </ul>
 * </p>
 * 
 * @author Scheller Sanchez
 * 
 */
public class RewardImprovedCoverage extends Event {

  /** Support serialization. */
  private static final long serialVersionUID = 1L;
  /** The name of the bonus. */
  private String bonusScoreName = "bonus";
  /** The last stored value for coverage. */
  private int lastCoverage = 0;
  /** Whether or not we have a prior coverage value for this team and match. */
  boolean hasLastCoverage = false;
  /** The unique name for this event. */
  public static final String eventName = "Reward Improved Coverage";
  /** The unique description for this event. */
  public static final String description = "Reward teams that improve their coverage.";
//  /** The property key for the wakeup interval. */
//  public static final String wakeupIntervalKey = "WakeupInterval";
//  /** The actual wakeup interval. */
//  private int wakeupInterval;
  /** The property key for the type of coverage. */
  public static final String typeOfCoverageKey = "TypeOfCoverage";
  /** The actual type of coverage. */
  private String typeOfCoverage;
//  /** The property key for the base score weight. */
//  public static final String baseScoreWeightKey = "BaseScoreWeight";
//  /** The actual bonus score weight. */
//  private int baseScoreWeight;
  /** The property key for the percent improvement. */
  public static final String percentImprovementKey = "percentImprovement";
  /** The actual percent improvement. */
  private int percentImprovement;
  /** The property key for Bonus Percent Improvement. */
  public static final String bonusPercentImprovementKey = "BonusPercentImprovement";
  /** The actual Bonus Percent Improvement. */
  private int bonusPercentImprovement;
  /** The property key for Bonus Improved Days. */
  public static final String bonusImprovedDaysKey = "BonusImprovedDays";
  /** The actual Bonus Improved Days. */
  private int bonusImprovedDays;
  /** The property key for Bonus Score Weight. */
  public static final String bonusScoreWeightKey = "BonusScoreWeight";
  /** The actual Bonus Score Weight. */
  private int bonusScoreWeight;
  /** The number of streak days for bonus. */
  private int streak = 0;

  /**
   * Creates a new event for Reward Improved Coverage. Ensures all required properties are
   * available.
   * 
   * @throws EventException If the configuration cannot be created.
   */
  public RewardImprovedCoverage() throws EventException {
    super(eventName, description);
    try {
      typeOfCoverage = configManager.getProperty(eventName, typeOfCoverageKey);
      percentImprovement = Integer.parseInt(configManager.getProperty(eventName,
          percentImprovementKey));
      bonusPercentImprovement = Integer.parseInt(configManager.getProperty(eventName,
          bonusPercentImprovementKey));
      bonusImprovedDays = Integer.parseInt(configManager.getProperty(eventName,
          bonusImprovedDaysKey));
      bonusScoreWeight = Integer
          .parseInt(configManager.getProperty(eventName, bonusScoreWeightKey));
    }
    catch (Exception e) {
      throw new EventException("Error configuring properties", e);
    }
  }

  /**
   * Returns the total number of points for this event. The total is the base score times the base
   * score weight plus the bonus score times the bonus score weight.
   * 
   * @param score The event score.
   * @return The total points for this event given the current weights.
   */
  public int computePoints(EventScore score) {
    // return ((score.getBaseScore() * this.baseScoreWeight) +
    // (score.getBonusScore(firstTimeBonusName) * this.firstTimeWeight));
    return ((score.getBaseScore() * this.baseScoreWeight) + (score
        .getBonusScore(this.bonusScoreName) * this.bonusScoreWeight));
  }

  /**
   * Creates an EventScore instance for RewardImprovedCoverage.
   * 
   * @param match The match of interest.
   * @param team The team of interest.
   * @param timestamp the time at which this event is being run.
   * @return The EventScore.
   * @throws EventException If problems occur while processing this event.
   */
  public EventScore computeScore(Match match, Team team, XMLGregorianCalendar timestamp)
      throws EventException {
    // [0] create the default score, which awards no points.
    EventScore score = new EventScore(timestamp, match, team);
    // [1] find the project and the sensorbase client.
    Project project = match.getProject(team);
    // [2] get the Build sensor data for the previous interval of time.
    String projectName = project.getName();
    String owner = project.getOwner();
    XMLGregorianCalendar endTime = timestamp;
    XMLGregorianCalendar startTime = Tstamp.incrementDays(endTime, wakeupInterval * -1);
    HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
        String.format("Getting coverage  data for match %s and team %s", match.getName(), team
            .getName()));

    // [3] Retrieve the coverage for the day prior to the wakeup time.
    DailyProjectDataClient dpdClient = match.getDailyProjectDataClient(team);
    HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
        String.format("Getting DPD coverage data for match %s and team %s", match.getName(), team
            .getName()));
    CoverageDailyProjectData coverageDpd;
    try {
      coverageDpd = dpdClient.getCoverage(owner, projectName, startTime, typeOfCoverage);

    }
    catch (Exception e) {
      throw new EventException(String.format("Error getting coverage for match %s and team %s",
          match.getName(), team.getName()), e);
    }

    // [4] if no coverage.
    if (!coverageDpd.hasCoverageData()) {
      score.setExplanation("No coverage no score.");
      return score;
    }

    // [5] there is coverage data, so compute the score.
    int currCoverage = coverageDpd.getPercentageCoverage();
    HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
        typeOfCoverage + " coverage is: " + currCoverage);
    score.setBaseScore(computeBaseScore(score, currCoverage));
    score.addBonusScore(bonusScoreName, computeBonusScore(score));

    // [6] now that we've computed the score, store coverage for the next time we run this event.
    lastCoverage = currCoverage;
    this.hasLastCoverage = true;
    // Finally return the final score.
    return score;
  }

  /**
   * Computes the base score given the current coverage. If there is no prior coverage, return 0. If
   * the current coverage is under the percentImprovement, return 0. Otherwise return between -1 and
   * +1 depending upon change in coverage.
   * 
   * @param score The EventScore.
   * @param currCoverage The current coverage percentage.
   * @return The base score.
   */
  private int computeBaseScore(EventScore score, int currCoverage) {
    if (!this.hasLastCoverage) {
      score.setExplanation("No prior coverage. Base score is 0.");
      return 0;
    }

    // Keeps track of streak for bonus.
    if ((currCoverage - lastCoverage) >= bonusPercentImprovement) {
      streak++;
    }
    else {
      streak = 0;
    }

    // Compare current coverage with the last coverage.
    if ((currCoverage - lastCoverage) >= (this.percentImprovement)) {
      score.setExplanation("Current coverage is better than last coverage. Base score is 1.");
      return 1;
    }
    if ((currCoverage - lastCoverage) <= (-(this.percentImprovement))) {
      score.setExplanation("Current coverage has declined by same amount "
          + "as it should increase. Base score is -1");
      return -1;
    }
    if (currCoverage == lastCoverage) {
      score.setExplanation("Current coverage and last coverage are the same. No points.");
      return 0;
    }
    else {
      return 0;
    }

  }

  /**
   * Computes the bonus score.
   * 
   * @param score The event score.
   * @return The bonus score.
   */
  private int computeBonusScore(EventScore score) {
    if (streak >= bonusImprovedDays) {
      score.setExplanation("Bonus score for increasing coverage by " + bonusPercentImprovement
          + "% for " + bonusImprovedDays + " days");
      return 1;
    }
    else {
      return 0;
    }
  }
}
